/******************************************************************************\
    Auxiliary.cpp contains plugin-depended functions.
    It's used with acm server plugins. See DllMain.cpp for details.
    Copyright (C) 2008  Victor Vinogradov (fly3333@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
\******************************************************************************/

#include <winsock2.h>
#include <windows.h>
#include ".\Auxiliary.h"
#include <Common\String.h>
#include "..\Post.h"
#include <Common\Log.h>
#include <Common\KMP.h>
#include <Common\Random.h>
#include <Common\Memory.hpp>
#include <Common\Plugin.h>
#include <set>
#include <vector>
#include <queue>
using namespace std;

extern cLog Log;

cCriticalSection AboutCriticalSection;
const char Title[] = "Дистанционный практикум по программированию кафедры АВТ ВоГТУ plugin " __DATE__;
const char Comment[] = "<a href='http://atpp.vstu.edu.ru/cgi-bin/arh_problems.pl'>Дистанционный практикум по программированию кафедры АВТ ВоГТУ</a>";

cCriticalSection LanguagesCriticalSection;
map< unsigned long, unsigned long > LanguageIndex;
vector< sLanguage > Language;

cCriticalSection ProblemsCriticalSection;
map< unsigned long, unsigned long > ProblemIndex;
vector< sProblem > Problem;

static const char Host[] = "atpp.vstu.edu.ru";

cCriticalSection VariablesCriticalSection;
const unsigned long VariablesCount = 3;
const char *VariableProperties[ VariablesCount ] =
{
	"Name = Login;",
	"Name = Password;",
	"Name = Language;\
	 Option\
	 [\
	   { Title = English; Value = en; },\
	   { Title = Russian; Value = ru; }\
	 ]"
};
string VariableValue[ VariablesCount ];
static string &Login = VariableValue[ 0 ];
static string &Password = VariableValue[ 1 ];
static string &Lang = VariableValue[ 2 ];

cCriticalSection CompilationErrorCriticalSection;
std::map< unsigned long, std::string > CompilationError;

static cCriticalData< bool > IsLoggedIn( false );
static cCriticalData< string > SessionID( "" );

cCriticalSection UserIdCriticalSection;
unsigned long UserId = 0;

static const unsigned CP1251 = 1251u;

// "Authorization: Basic %s\r\n", Base64Encode( Login + ":" + Password ).c_str() );

static string GetLang()
{
	cLocker locker( VariablesCriticalSection );
	string lang = "en";
	if( Lang == "ru" )
		lang = "ru";
	return lang;
}

static string GetAuth()
{
	cLocker locker( VariablesCriticalSection );
	return "Basic " + Base64Encode( Login + ":" + Password );
}

static map< string, string > GetHeaders()
{
	map< string, string > header;
	header[ "Authorization" ] = GetAuth();
	return header;
}

unsigned long GetUserId()
{
	cLocker locker( UserIdCriticalSection );
	try
	{
		if( UserId == 0 )
		{
			string response;
			if( !HttpGet( Host, "http://atpp.vstu.edu.ru/cgi-bin/status.pl", response, GetHeaders() ) )
			{
				Log.Error( "GetUserId::HttpGet returned false.\n" );
				return 0;
			}

			const char *str = response.c_str();
			int len = ( int )response.length();
			int pos = 0, new_pos;

			static const cCIKMPString key_str( "You logged on system as <a href=\"/cgi-bin/statistica.pl?id_publ=" );
			new_pos = key_str.FindIn( str + pos, len - pos );
			if( new_pos == -1 )
			{
				Log.Error( "GetUserId: Unable to find user id." );
				return 0;
			}
			pos += new_pos + key_str.Length();
			unsigned long user_id;
			sscanf( str + pos, "%lu%n", &user_id, &new_pos );
			pos += new_pos;

			UserId = user_id;
		}
		return UserId;
	}
	catch( ... )
	{
		Log.Error( "GetUserId: exception O.o\n" );
		return 0;
	}
}

bool LoadProblems()
{
	cLocker locker( ProblemsCriticalSection );
	try
	{
		static bool is_problems_loaded = false;
		if( is_problems_loaded )
			return true;

		Problem.clear();
		ProblemIndex.clear();

		string response;

		set< int > theme_id_set;
		queue< int > theme_id_queue;
		theme_id_set.insert( -1 );
		theme_id_queue.push( -1 );
		while( !theme_id_queue.empty() )
		{
			int theme_id = theme_id_queue.front();
			theme_id_queue.pop();
			if( !HttpGet( Host, StringPrintf( "http://atpp.vstu.edu.ru/cgi-bin/arh_problems.pl?id_tm=%d&id_lng=%s", theme_id, GetLang().c_str() ), response ) )
			{
				Log.Error( "LoadProblems::HttpGet returned false.\n" );
				return false;
			}

			int pos = 0, new_pos;
			const char *str = response.c_str();
			int str_length = ( int )response.length();

			while( true )
			{
				static const cCIKMPString link_str( "<a href=/cgi-bin/arh_problems.pl?id_" );
				new_pos = link_str.FindIn( str + pos, str_length - pos );
				if( new_pos == -1 )
					break;
				pos += new_pos + link_str.Length();
				new_pos = strchr( str + pos, '=' ) - ( str + pos );
				string type( str + pos, new_pos );
				pos += new_pos + 1;

				unsigned long id;
				sscanf( str + pos, "%lu%n", &id, &new_pos );
				pos += new_pos;

				if( type == "tm" )
				{
					if( theme_id_set.find( id ) == theme_id_set.end() )
					{
						theme_id_set.insert( id );
						theme_id_queue.push( id );
					}
				}
				else if( type == "prb" )
				{
					new_pos = strchr( str + pos, '>' ) - ( str + pos );
					pos += new_pos + 1;
					new_pos = strchr( str + pos, '<' ) - ( str + pos );
					string header( str + pos, new_pos );
					pos += new_pos + 1;

					header = ConvertCodePage( Trim( header ), CP1251, CP_UTF8 );

					ProblemIndex.insert( make_pair( id, Problem.size() ) );
					Problem.push_back( sProblem( id, header ) );
				}
			}
		}

		if( Problem.size() == 0 )
		{
			Log.Error( "LoadProblems: Condition (Problem.size()==0) is true.\n" );
			return false;
		}

		is_problems_loaded = true;
		return true;
	}
	catch( ... )
	{
		Log.Error( "LoadProblems: exception O.o\n" );
		return false;
	}
}

bool sProblem::Load()
{
	cLocker locker( ProblemsCriticalSection );
	try
	{
		if( IsLoaded )
			return true;

		// Загружаем задачку
		if( !HttpGet( Host, StringPrintf( "http://atpp.vstu.edu.ru/cgi-bin/arh_problems.pl?id_prb=%lu&id_lng=%s", Id, GetLang().c_str() ), Body ) )
		{
			Log.Error( "sProblem::Load::HttpGet returned false.\n" );
			return false;
		}

		if( !Cut( Body, "<h2 align=\"center\">", "<hr class=\"hr_down_prb\">" ) )
		{
			Log.Error( "sProblem::Load: Cut returned false." );
			return false;
		}

		Body = ConvertCodePage( Body, CP1251, CP_UTF8 );

		string header = StringPrintf( "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><title>%s</title><link rel=\"STYLESHEET\" type=\"text/css\" href=\"/main.css\"><link rel=\"STYLESHEET\" type=\"text/css\" href=\"/color_lang.css\"></head><body><table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" height=\"100%%\" width=\"100%%\"><tr height=\"100%%\"><td valign=\"top\"><h2 align=\"center\">", Header.c_str() );
		string tail = "</td></tr></table></body></html>";
		Body = header + Body + tail;

		// Ищем изображения
		if( !LoadAdditions( Host, "/", Body, AdditionId ) )
		{
			Log.Error( "sProblem::Load::LoadAdditions returned false.\n" );
			return false;
		}

		IsLoaded = true;
		return true;
	}
	catch( ... )
	{
		Log.Error( "sProblem::Load: exception O.o\n" );
		return false;
	}
}

bool LoadLanguages()
{
	cLocker locker( LanguagesCriticalSection );
	try
	{
		static bool is_languages_loaded = false;
		if( is_languages_loaded )
			return true;

		string response;

		if( !HttpGet( Host, StringPrintf( "http://atpp.vstu.edu.ru/cgi-bin/submit.pl?id_lng=en" ), response ) )
		{
			Log.Error( "LoadLanguages::HttpGet returned false.\n" );
			return false;
		}

		if( !Cut( response, "<select name=\"id_compiler\" size=\"1\" style=\"width: 500;\">", "</select>" ) )
		{
			Log.Error( "LoadLanguages::Cut returned false.\n" );
			return false;
		}

		int pos = 0, new_pos;
		const char *str = response.c_str();
		int str_length = ( int )response.length();

		while( true )
		{
			static const cCIKMPString keystr( "<OPTION VALUE=\"" );
			new_pos = keystr.FindIn( str + pos, str_length - pos );
			if( new_pos == -1 )
				break;
			pos += new_pos + keystr.Length();

			new_pos = strchr( str + pos, '"' ) - ( str + pos );
			unsigned long id = 0;
			sscanf( str + pos, "%lu%n", &id, &new_pos );
			pos += new_pos;

			new_pos = strchr( str + pos, '>' ) - ( str + pos );
			pos += new_pos + 1;
			new_pos = strchr( str + pos, '<' ) - ( str + pos );
			string title( str + pos, new_pos );
			pos += new_pos + 1;

			LanguageIndex.insert( make_pair( id, Language.size() ) );
			Language.push_back( sLanguage( id, title ) );
		}

		if( Language.size() == 0 )
		{
			Log.Error( "LoadLanguages: Condition (Language.size()==0) is true.\n" );
			return false;
		}

		is_languages_loaded = true;
		return true;
	}
	catch( ... )
	{
		Log.Error( "LoadLanguages: exception O.o\n" );
		return false;
	}
}

bool sProblem::GetLastSubmit( long &result_code, unsigned short &test_number, unsigned long &time, unsigned long &memory )
{
	try
	{
		LastSubmitId = 0;
		result_code = RESULT_SUBMISSION_ERROR;
		test_number = 0;
		time = 0;
		memory = 0;

		string response;

		if( !HttpGet( Host, StringPrintf( "http://atpp.vstu.edu.ru/cgi-bin/status.pl?id_publ=%lu&id_prb=%lu&id_lng=en", GetUserId(), Id ), response, GetHeaders() ) )
		{
			Log.Error( "GetLastSubmit::HttpGet returned false.\n" );
			return false;
		}

		int pos = 0, new_pos;
		const char *str = response.c_str();
		int len = ( int )response.length();

		static const cCIKMPString begin_str( "<tr><td align=\"left\" nowrap>&nbsp;" );
		new_pos = begin_str.FindIn( str + pos, len - pos );
		if( new_pos == -1 )
			return true;

		pos += new_pos + begin_str.Length();
		if( sscanf( str + pos, "%lu", &LastSubmitId ) != 1 )
			return false;

		static const cCIKMPString result_str( "<div style=\"float:left;height:30px;\"></div>" );
		static const cCIKMPString result_end_str( "</div>" );
		new_pos = result_str.FindIn( str + pos, len - pos );
		if( new_pos == -1 )
			return false;
		pos += new_pos + result_str.Length();
		new_pos = result_end_str.FindIn( str + pos, len - pos );
		string result( str + pos, new_pos );
		pos += new_pos + result_end_str.Length();

		static const cCIKMPString data_str( "<td align=\"center\">" );

		new_pos = data_str.FindIn( str + pos, len - pos );
		pos += new_pos + data_str.Length();
		unsigned short _test_number;
		if( sscanf( str + pos, "%hu", &_test_number ) == 1 )
			test_number = _test_number;

		new_pos = data_str.FindIn( str + pos, len - pos );
		pos += new_pos + data_str.Length();
		double _time;
		if( sscanf( str + pos, "%lf", &_time) == 1 )
			time = ( unsigned long )( _time * 1000 );

		new_pos = data_str.FindIn( str + pos, len - pos );
		pos += new_pos + data_str.Length();
		unsigned long _memory;
		if( sscanf( str + pos, "%lu", &_memory ) == 1 )
			memory = _memory * 1024;

		if( result == "Running" || 
			 result == "Compiling" )
			result_code = RESULT_RUNNING;
		else if( result == "<b>Accepted</b>" )
			result_code = RESULT_ACCEPTED;
		else if( result == "Memory limit" )
			result_code = RESULT_MEMORY_LIMIT_EXCEEDED;
		else if( result == "Wrong answer" )
			result_code = RESULT_WRONG_ANSWER;
		else if( result == "Time limit" )
			result_code = RESULT_TIME_LIMIT_EXCEEDED;
		else if( result == "Compilation error" )
			result_code = RESULT_COMPILATION_ERROR;
		else if( result == "Sleep detect" ||
			      result == "Security violation" )
			result_code = RESULT_SECURITY_VIOLATION;
		else if( result == "Run time error" )
			result_code = RESULT_RUN_TIME_ERROR;
		else if( result == "Presentation Error" )
			result_code = RESULT_PRESENTATION_ERROR;
		else 
		{
			Log.Error( "SubSubmit: Unknown result \"%s\"\n", result.c_str() );
			return false;
		}
	
		Log.Notice( "GetLastSubmit: Last submit id = %lu\n", LastSubmitId );
		return true;
	}
	catch( ... )
	{
		Log.Error( "GetLastSubmit: Exception O.o\n" );
		return false;
	}
}

static bool GetLastSubmit( unsigned long problem_index, unsigned long &last_submit_id,  long &result_code, unsigned short &test_number, unsigned long &time, unsigned long &memory )
{
	cLocker locker( ProblemsCriticalSection );
	if( !Problem[ problem_index ].GetLastSubmit( result_code, test_number, time, memory ) )
		return false;
	last_submit_id = Problem[ problem_index ].LastSubmitId;
	return true;
}

bool SubSubmit( unsigned long problem_index, unsigned long language_index, const string &source, long &result, unsigned short &test_number, unsigned long &time, unsigned long &memory )
{
	static cCriticalSection submit_critical_section;
	cLocker locker( submit_critical_section );
	try
	{
		string response, location;

		unsigned long problem_id;
		{
			cLocker locker( ProblemsCriticalSection );
			problem_id = Problem[ problem_index ].Id;
		}

		unsigned long language_id;
		{
			cLocker locker( LanguagesCriticalSection );
			language_id = Language[ language_index ].Id;
		}

		unsigned long last_submit_id; 
		if( !GetLastSubmit( problem_index, last_submit_id, result, test_number, time, memory ) )
		{
			Log.Error( "SubSubmit: GetLastSubmit returned false." );
			return false;
		}

		if( !HttpPost( Host, "http://atpp.vstu.edu.ru/cgi-bin/submit.pl", StringPrintf( "mode=send&id_prb=%lu&id_compiler=%lu&source=%s", problem_id, language_id, UrlEncode( source ).c_str() ), response, location, GetHeaders() ) )
		{
			Log.Error( "SubSubmit::HttpPost returned false.\n" );
			return false;
		}

		if( location != "http://atpp.vstu.edu.ru/cgi-bin/status.pl" )
		{
			Log.Error( "SubSubmit: Submission was not successful. Wrong location: %s\n", location.c_str() );
			return false;
		}

		const int MAX_COUNT = 100;
		int count = 0;

		while( true )
		{
			unsigned long submit_id;
			if( !GetLastSubmit( problem_index, submit_id, result, test_number, time, memory ) )
			{
				Log.Error( "SubSubmit: GetLastSubmit returned false." );
				return false;
			}

			if( submit_id == last_submit_id )
			{
				++count;
				if( count == MAX_COUNT )
				{
					Log.Error( "SubSubmit: Too many loops.\n" );
					return false;
				}
				Sleep( 100 );
				continue;
			}

			if( result == RESULT_RUNNING )
			{
				++count;
				if( count == MAX_COUNT )
				{
					Log.Error( "SubSubmit: Too many loops.\n" );
					return false;
				}
				Sleep( 100 );
				continue;
			}

			{
				cLocker locker( ProblemsCriticalSection );
				Problem[ problem_index ].LastSubmitId = submit_id;
			}
			
			if( result == RESULT_COMPILATION_ERROR )
			{
				if( !HttpGet( Host, StringPrintf( "http://atpp.vstu.edu.ru/cgi-bin/status.pl?mode=report&id_stat=%lu", submit_id ), response, GetHeaders() ) )
				{
					Log.Error( "SubSubmit::HttpGet returned false.\n" );
					return false;
				}

				if( !Cut( response, "<td class=\"rpt_text\">", "</td>" ) )
				{
					Log.Error( "SubSubmit: Unable to find compilation error." );
					return false;
				}

				response = Concat( Split( response, "<br/>" ), "\r\n" );

				{
					cLocker locker( CompilationErrorCriticalSection );
					CompilationError[ GetCurrentThreadId() ] = response;
				}
			}
			break;
		}         

		return true;
	}
	catch( ... )
	{
		Log.Error( "SubSubmit: exception O.o\n" );
		return false;
	}
}
